package org.onionframework.persistence.dao;

import java.io.Serializable;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import org.hibernate.Criteria;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.criterion.CriteriaSpecification;
import org.hibernate.criterion.Projection;
import org.hibernate.criterion.Projections;
import org.hibernate.internal.CriteriaImpl;
import org.hibernate.transform.ResultTransformer;
import org.onionframework.core.util.Reflections;
import org.onionframework.persistence.dao.dynamic.NamedQueryBuilder;
import org.onionframework.persistence.dao.dynamic.StatementTemplateSupport;
import org.onionframework.persistence.query.OnionQuery;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.Assert;

public class CommonDaoImpl implements CommonDao {

	private static final Logger logger = LoggerFactory.getLogger(CommonDaoImpl.class);
	
	protected SessionFactory sessionFactory;
	
	@Autowired
	protected StatementTemplateSupport statementTemplateSupport;
	
	/**
	 * 使用sessionFactory.getCurrentSession()获取session，
	 * 无需自己管理session。实际上session的打开、关闭都是在
	 * transactionManager里面做的。
	 * 
	 * @return Hibernate Session 实例
	 */
	protected Session getSession() {
		return this.sessionFactory.getCurrentSession();
	}

	public void setSessionFactory(SessionFactory sessionFactory) {
		this.sessionFactory = sessionFactory;
	}
	
	/* (non-Javadoc)
	 * @see org.onionframework.persistence.dao.CommonDao#save(java.lang.Object)
	 */
	@Override
	public void save(Object object) {
		getSession().saveOrUpdate(object);
	}
	
	/* (non-Javadoc)
	 * @see org.onionframework.persistence.dao.CommonDao#saveAll(java.util.Collection)
	 */
	@Override
	public void saveAll(Collection<?> objects){
		for(Object o : objects)
			this.save(o);
	}

	/* (non-Javadoc)
	 * @see org.onionframework.persistence.dao.CommonDao#merge(java.lang.Object)
	 */
	@Override
	public void merge(Object object) {
		getSession().merge(object);
	}

	/* (non-Javadoc)
	 * @see org.onionframework.persistence.dao.CommonDao#evict(java.lang.Object)
	 */
	@Override
	public void evict(Object object) {
		getSession().evict(object);
	}
	
	/* (non-Javadoc)
	 * @see org.onionframework.persistence.dao.CommonDao#remove(java.lang.Object)
	 */
	@Override
	public void remove(Object o) {
		this.getSession().delete(o);
	}

	/* (non-Javadoc)
	 * @see org.onionframework.persistence.dao.CommonDao#flush()
	 */
	@Override
	public void flush(){
		this.getSession().flush();
	}
	
	/* (non-Javadoc)
	 * @see org.onionframework.persistence.dao.CommonDao#refresh(java.lang.Object)
	 */
	@Override
	public void refresh(Object o) {
		this.getSession().refresh(o);
	}
	
	@SuppressWarnings("unchecked")
	protected <T> T get(Class<T> entityClass,Serializable id) {
		T entity = (T)this.getSession().get(entityClass, id);
		return entity == null ? null : (T) entity;
	}

	@SuppressWarnings("unchecked")
	protected <T> T load(Class<T> entityClass,Serializable id) {
		return (T) this.getSession().load(entityClass, id);
	}

	protected void removeById(Class<?> entityClass,Serializable id) {
		this.getSession().delete(this.load(entityClass,id));
	}
	
	@SuppressWarnings("unchecked")
	protected <T> List<T> findAllList(Class<T> entityClass) {
		return this.getSession().createCriteria(entityClass).list();
	}
	
	@SuppressWarnings("unchecked")
	protected <T> List<T> findList(String hql, Object... values) {
		Query query = this.getSession().createQuery(hql);
		for (int i = 0; i < values.length; i++)
			query.setParameter(i, values[i]);
		return query.list();
	}

	@SuppressWarnings("unchecked")
	protected <T> List<T> findPage(String hql, int start, int maxResults,
			Object... parameters) {
		Query query = createQuery(hql, parameters).setFirstResult(start);
		
		if(maxResults > 0)
			query.setMaxResults(maxResults).list();
		
		return query.list();
	}
	
	/* (non-Javadoc)
	 * @see org.onionframework.persistence.dao.CommonDao#findList(org.onionframework.persistence.query.OnionQuery)
	 */
	@Override
	@SuppressWarnings("unchecked")
	public <E> List<E> findList(OnionQuery onionQuery){
		if(onionQuery.isNamedQuery()){
			Query query = new NamedQueryBuilder(this.statementTemplateSupport, this.getSession()).build(onionQuery);
			return query.list();
		}else{
			Criteria criteria = this.createCriteria(onionQuery);
			return criteria.list();
		}
	}
	
	/* (non-Javadoc)
	 * @see org.onionframework.persistence.dao.CommonDao#findUnique(org.onionframework.persistence.query.OnionQuery)
	 */
	@Override
	@SuppressWarnings("unchecked")
	public <T> T findUnique(OnionQuery onionQuery) {
		if(onionQuery.isNamedQuery()){
			Query query = new NamedQueryBuilder(this.statementTemplateSupport, this.getSession()).build(onionQuery);
			
			return (T) query.uniqueResult();
		}else{
			Criteria criteria = new CriteriaBuilder(this.getSession())
				.build(onionQuery);
		
			return (T) criteria.uniqueResult();
		}
	}

	@SuppressWarnings("unchecked")
	protected Long count(String hql, Object... values) {
		Query query = this.getSession().createQuery(hql);
		for (int i = 0; i < values.length; i++)
			query.setParameter(i, values[i]);

		List<Long> countlist = query.list();

		return (Long) countlist.get(0);
	}
	
	/* (non-Javadoc)
	 * @see org.onionframework.persistence.dao.CommonDao#count(org.onionframework.persistence.query.OnionQuery)
	 */
	@Override
	public Long count(OnionQuery query) {
		if(query.isNamedQuery()){
			Number count = (Number) new NamedQueryBuilder(this.statementTemplateSupport, this.getSession()).buildCount(query).uniqueResult();
			return count.longValue();
		}else
			return countByCriteria(this.createCriteria(query));
	}
	
	@SuppressWarnings("unchecked")
	protected Long countByCriteria(final Criteria criteria) {
		CriteriaImpl impl = (CriteriaImpl) criteria;

		Projection projection = impl.getProjection();
		ResultTransformer transformer = impl.getResultTransformer();

		List<CriteriaImpl.OrderEntry> orderEntries = null;
		try {
			orderEntries = (List<CriteriaImpl.OrderEntry>) Reflections.getFieldValue(impl,
					"orderEntries");
			Reflections
					.setFieldValue(impl, "orderEntries", new ArrayList<CriteriaImpl.OrderEntry>());
		} catch (Exception e) {
			logger.error("反射异常", e);
		}

		Long totalCount = (Long) criteria.setProjection(Projections.rowCount())
				.uniqueResult();

		totalCount = totalCount == null ? 0 : totalCount;

		criteria.setProjection(projection);

		if (projection == null) {
			criteria.setResultTransformer(CriteriaSpecification.ROOT_ENTITY);
		}
		if (transformer != null) {
			criteria.setResultTransformer(transformer);
		}
		try {
			Reflections.setFieldValue(impl, "orderEntries", orderEntries);
		} catch (Exception e) {
			logger.error("反射异常", e);
		}

		return totalCount;
	}
	
	protected Criteria createCriteria(OnionQuery query) {
		return new CriteriaBuilder(this.getSession()).build(query);
	}
	
	protected Query createQuery(String hql, Object... values) {
		Assert.hasText(hql);
		Query query = getSession().createQuery(hql);
		for (int i = 0; i < values.length; i++) {
			query.setParameter(i, values[i]);
		}
		
		return query;
	}
	
}
